
// @ts-ignore
import Modeler from "bpmn-js/lib/Modeler";
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
// @ts-ignore
import BpmnMinimapModule from 'diagram-js-minimap';
import 'diagram-js-minimap/assets/diagram-js-minimap.css'
// @ts-ignore
import BpmnSimulationModule from "bpmn-js-token-simulation";
import 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css'
// 翻译
import TranslationModule from './modules/translations'
// flowable解析扩展
// @ts-ignore
import flowableDescriptorModule from './modules/descriptor/flowable';
import flowableDescriptor from './modules/descriptor/flowable/descriptor.json';
// 自定义元素选中时的弹出菜单（修改 默认任务 为 用户任务）
// @ts-ignore
import CustomEleMenu from './modules/CustomEleMenu'
// 自定义面板（修改 默认任务 为 用户任务）
// @ts-ignore
import CustomPalette from './modules/CustomPalette'

export default class BpmnModeler {
  /** bpmn.js的Modeler */
  private modeler?: Modeler;

  /**
   * 构造方法
   * @param container Bpmn的绘图容器
   */
  constructor(container:HTMLElement) {
    this.modeler = new Modeler({
      container: container,
      keyboard: {bindTo: window},
      additionalModules: [
        BpmnMinimapModule,
        BpmnSimulationModule,
        TranslationModule.zhCN,
        CustomPalette,
        CustomEleMenu,
        flowableDescriptorModule
      ],
      moddleExtensions: {
        flowable: flowableDescriptor
      }
      ,
      // taskResizingEnabled: true,
      // eventResizingEnabled: true,
      minimap: {
        open: true
      }
    });
    this.getEventBus().on("commandStack.changed", (e:any) => {
      this.onCommandStackChangedListeners.forEach((listener)=>{listener(e)});
    });
    this.modeler.on("canvas.viewbox.changed", (e:any) => {
      this.onCanvasViewboxChangedListeners.forEach((listener)=>{listener(e)});
    });
    this.modeler.on("import.done", (e:any) => {
      this.onImportDoneListeners.forEach((listener)=>{listener(e)});
    });
    this.modeler.on("selection.changed", (e:any) => {
      this.onSelectionChangedListeners.forEach((listener)=>{listener(e)});
    });
    this.modeler.on("element.changed", (e:any) => {
      this.onElementChangedListeners.forEach((listener)=>{listener(e)});
    });

    this.importBpmnContent();
  }

  /** 获取bpmn.js的Modeler */
  public getModeler=():any=>this.modeler;
  public get=(name:string):any=>this.modeler.get(name);
  public getToggleMode=()=>this.get("toggleMode");
  public getSelection=()=>this.get("selection");
  public getAlignElements=()=>this.get("alignElements");
  public getCanvas=()=>this.get("canvas");
  public getCommandStack=()=>this.get("commandStack");
  public getEventBus=()=>this.get("eventBus");
  public getElementRegistry=()=>this.get("elementRegistry");
  public getModeling=()=>this.get("modeling");
  public getModdle=()=>this.get("moddle");
  public getBpmnFactory=()=>this.get("bpmnFactory");
  public getElementFactory=()=>this.get("elementFactory");
  public getReplace=()=>this.get("replace");
  public getDefinitions=()=>this.modeler.getDefinitions();


  /**
   * 导入Bpmn图表XML内容
   * @param xml XML格式的图表内容
   */
  public importBpmnContent=(xml?:string)=>{
    let definitionId = `definition_${new Date().getTime()}`
    let processId = `process_${new Date().getTime()}`;
    let processName = `业务流程_${new Date().getTime()}`;
    let xmlString = (xml && xml.length>0)?xml: `<?xml version="1.0" encoding="UTF-8"?>
      <bpmn2:definitions 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
        xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
        xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
        xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
        targetNamespace="http://flowable.org/bpmn">
        <bpmn2:process id="${processId}" name="${processName}" isExecutable="true">
        </bpmn2:process>
        <bpmndi:BPMNDiagram id="BPMNDiagram_1">
          <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${processId}">
          </bpmndi:BPMNPlane>
        </bpmndi:BPMNDiagram>
      </bpmn2:definitions>`;
    this.modeler.importXML(xmlString).then(({ warnings }:any)=>{
      warnings.forEach((warn:any) => console.warn(warn));
    }).catch((e:any)=>{
      console.error(`[Process Designer Warn]: ${e?.message || e}`);
    })
  }

  /** 导出Bpmn图表XML内容 */
  public exportBpmnContent=()=> this.modeler.saveXML({format:true})

  /** 导入本地BPMN流程图文件 */
  public importBpmnFile=()=>{
    const input = document.createElement('input');
    input.value = '选择文件';
    input.type = 'file';
    input.accept = 'application/xml';
    input.onchange = (event:any) => {
      if(event.target.files.length>0){
        const reader = new FileReader();
        reader.readAsText(event.target.files[0]);
        reader.onload = ()=>{
          this.importBpmnContent(reader.result as string);
        };
      }
    };
    input.click();
  }

  /** 导出BPMN流程图文件到本地 */
  public exportBpmnFile=()=>{
    this.exportBpmnContent().then(({xml}:any)=>{
      // TODO 文件名
      const filename = "diagram.bpmn20.xml";
      const downlink = `data:application/bpmn20-xml;charset=UTF-8,${encodeURIComponent(xml)}`;
      const a = document.createElement("a");
      a.download = filename; //指定下载的文件名
      a.href = downlink; //  URL对象
      a.click(); // 模拟点击
      URL.revokeObjectURL(a.href); // 释放URL 对象
    }).catch((err:any)=>{
      console.error(`[Process Designer Warn ]: ${err.message || err}`);
    })
  }

  /** 切换模拟流程流转 */
  public toggleSimulation=()=>{
    this.getToggleMode().toggleMode();
  }

  /** 对齐选中的元素 */
  public alignSelectedElement=(type:'left'|'right'|'top'|'bottom'|'center'|'middle')=>{
    const align = this.getAlignElements();
    const selectedElements = this.getSelection().get();
    if(selectedElements && selectedElements.length >= 2){
      align.trigger(selectedElements, type);
    }
  }

  /** 获取首个选中的元素，如果没有则返回外层流程定义的元素 */
  public getSelectedElement=()=>{
    const selectedElements = this.getSelection().get();
    if(selectedElements && selectedElements.length>0){
      return selectedElements[0];
    }
    const elementRegistry = this.getElementRegistry();
    return elementRegistry.find((e:any) => e.type === "bpmn:Process")
      || elementRegistry.find((e:any) => e.type === "bpmn:Collaboration");
  }

  /** 缩小/放大/重置 */
  public zoomModelerCanvas=(zoom:number|'RESET')=>{
    if('RESET'===zoom){
      this.getCanvas().zoom("fit-viewport", "auto");
    }else{
      this.getCanvas().zoom(zoom);
    }
  }

  /** 撤销 */
  public undo=()=>{this.getCommandStack().undo();}
  public canUndo=()=>this.getCommandStack().canUndo();
  /** 重做 */
  public redo=()=>{this.getCommandStack().redo();}
  public canRedo=()=>this.getCommandStack().canRedo();
  /** 重新加载 */
  public reload=()=>{this.importBpmnContent();}

  /** 监听导入完成事件 */
  private onImportDoneListeners = new Set<any>();
  public onImportDone=(listener:(e:any)=>void)=>{this.onImportDoneListeners.add(listener);}
  /** 监听选择事件 */
  private onSelectionChangedListeners = new Set<any>();
  public  onSelectionChanged=(listener:(e:any)=>void)=>{this.onSelectionChangedListeners.add(listener);}
  /** 监听元素修改事件 */
  private onElementChangedListeners = new Set<any>();
  public onElementChanged=(listener:(e:any)=>void)=>{this.onElementChangedListeners.add(listener);}
  /** 监听图形改变 */
  private onCommandStackChangedListeners = new Set<any>();
  public onCommandStackChanged=(listener:(e:any)=>void)=>{this.onCommandStackChangedListeners.add(listener);}
  /** 监听视图缩放变化 */
  private onCanvasViewboxChangedListeners = new Set<any>();
  public onCanvasViewboxChanged=(listener:(e:any)=>void)=>{this.onCanvasViewboxChangedListeners.add(listener);}


}
